﻿//  Copyright (c) 2011 Ray Liang (http://www.dotnetage.com)
//  Dual licensed under the MIT and GPL licenses:
//  http://www.opensource.org/licenses/mit-license.php
//  http://www.gnu.org/licenses/gpl.html

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.UI;
using System.Linq.Expressions;
using System.Web.Script.Serialization;
using System.IO;
using System.Web.WebPages;

namespace DNA.Mvc.jQuery
{
    public abstract class DataListBuilder<TModel, TComponent, TOptions, TBuilder> : jQueryDataListComponentBuilder<TModel, TComponent, NavigableView, TOptions, TBuilder>
        where TModel : class
        where TComponent : RemoteDataBindableContainer<TModel, NavigableView>
        where TBuilder : DataListBuilder<TModel, TComponent, TOptions, TBuilder>
    {
        public DataListBuilder(TComponent component, ViewContext viewContext, IViewDataContainer viewContainer) :
            base(component, viewContext, viewContainer) { }

        protected Func<TModel, dynamic> dataValueField;

        protected Func<TModel, dynamic> dataTextField;

        public TBuilder DataTextField(Expression<Func<TModel, dynamic>> textFieldSelector)
        {
            dataTextField = textFieldSelector.Compile();
            return this as TBuilder;
        }

        public TBuilder DataValueField(Expression<Func<TModel, dynamic>> valueFieldSelector)
        {
            dataValueField = valueFieldSelector.Compile();
            return this as TBuilder;
        }

        public TBuilder Bind(IEnumerable<TModel> model, Expression<Func<TModel, dynamic>> textFieldSelector,
            Expression<Func<TModel, dynamic>> valueFieldSelector)
        {
            dataTextField = textFieldSelector.Compile();
            dataValueField = valueFieldSelector.Compile();
            return Bind(model);
        }

        public TBuilder Items(Action<NavigableViewFactory> items)
        {
            var factory = new NavigableViewFactory(this.Component, this.ViewContext, this.ViewDataContainer);
            items.Invoke(factory);
            return this as TBuilder;
        }

        public TBuilder Bind(string remoteUrl)
        {
            return this.Bind(remoteUrl, "GET");
        }

        /// <summary>
        /// Specified remote url that contains json data
        /// </summary>
        /// <param name="remoteUrl"></param>
        /// <param name="httpMethod"></param>
        /// <returns></returns>
        public TBuilder Bind(string remoteUrl, string httpMethod)
        {
            if (string.IsNullOrEmpty(remoteUrl))
                throw new ArgumentNullException("remoteUrl");

            Component.RemoteDataSource = remoteUrl;

            if (!string.IsNullOrEmpty(httpMethod))
                Component.HttpMethod = httpMethod;

            return this as TBuilder;
        }

        /// <summary>
        /// Specified the client template for Razor.
        /// </summary>
        /// <param name="template"></param>
        /// <returns></returns>
        public TBuilder ClientItemTemplate(Func<object, object> template)
        {
            if (template != null)
                Component.ClientItemTemplate.InlineContent = template;
            return this as TBuilder;
        }

        ///// <summary>
        ///// Specified the client template in string format
        ///// </summary>
        ///// <param name="jQueryTemplate"></param>
        ///// <returns></returns>
        //public TBuilder ClientItemTemplate(Action<TextWriter> writer)
        //{
        //    Func<object, object> clientItemTmpl = new Func<object, object>(obj =>
        //    {
        //        return new HelperResult(writer);
        //    });
        //    return this.ClientItemTemplate(clientItemTmpl);
        //}

        /// <summary>
        /// Specified the client template for ASPX
        /// </summary>
        /// <param name="template"></param>
        /// <returns></returns>
        public TBuilder ClientItemTemplate(Action template)
        {
            if (template != null)
                Component.ClientItemTemplate.Content = template;
            return this as TBuilder;
        }

        protected virtual void OnServerDataBind(HtmlTextWriter writer)
        {
            if (Component.Model != null)
            {
                foreach (var modelItem in Component.Model)
                {
                    var item = new NavigableView();
                    item.Model = modelItem;

                    if (dataValueField != null)
                        item.Value = dataValueField.Invoke(modelItem);

                    if (dataTextField != null)
                        item.Title = dataTextField.Invoke(modelItem);

                    if (!Component.ItemTemplate.IsEmpty)
                    {
                        if (Component.ItemTemplate.Content != null)
                        {
                            item.Template.Content = new Action(() =>
                            {
                                Component.ItemTemplate.Content.Invoke(modelItem);
                            });
                        }

                        if (Component.ItemTemplate.InlineContent != null)
                        {
                            item.Template.InlineContent = new Func<object, object>(data =>
                            {
                                return Component.ItemTemplate.InlineContent.Invoke((TModel)data);
                            });
                        }
                    }

                    Component.AddItem(item);
                }
            }
        }

        protected virtual void OnJsonDataBind(string jsonData, HtmlTextWriter writer) { }

        protected virtual void OnRemoteDataBind(HtmlTextWriter writer) { }

        public override void Render()
        {
            GenerateId();
            using (var writer = new HtmlTextWriter(this.ViewContext.Writer))
            {
                _RenderCore(writer);
            }
        }

        public override MvcHtmlString GetHtml()
        {
            GenerateId();
            var result = new StringBuilder();
            using (var writer = new HtmlTextWriter(new StringWriter(result)))
            {
                _RenderCore(writer);
            }
            return MvcHtmlString.Create(result.ToString());
        }

        private void _RenderCore(HtmlTextWriter writer)
        {
            //Json binding and Server binding
            if (string.IsNullOrEmpty(Component.RemoteDataSource))
            {
                //Server binding
                if ((Component.ClientItemTemplate.IsEmpty) && (Component.Model != null))
                {
                    OnServerDataBind(writer);
                    RenderComponent(writer);
                    return;
                }

                //Json binding
                if ((!Component.ClientItemTemplate.IsEmpty) && ((Component.Model != null) || Component.HasItems))
                {
                    string jsonModel = "";
                    var serializer = new JavaScriptSerializer();
                    if (Component.Model != null)
                    {
                        jsonModel = serializer.Serialize(Component.Model);
                    }
                    else
                    {
                        var _model = (from item in Component.GetItems()
                                      select new NavigableData
                                      {
                                          Title = item.Title,
                                          Value = item.Value,
                                          ImageUrl = item.ImageUrl,
                                          NavigateUrl = item.NavigateUrl,
                                          Target = item.Target,
                                          ToolTip = item.ToolTip
                                      }
                                     ).ToList();
                        jsonModel = serializer.Serialize(_model);
                        Component.ClearItems();

                    }
                    RenderComponent(writer);
                    OnJsonDataBind(jsonModel, writer);
                }
                else
                {
                    RenderComponent(writer);
                }
            }
            else //Lazy binding (Load data on demond)
            {
                //Component.Render(writer);
                //Helper.jQuery(Component.Id, jQueryPluginName, options);
                OnRemoteDataBind(writer);
                //RenderComponent(writer);
            }
        }

        protected void RenderComponent(HtmlTextWriter writer)
        {
            Component.Render(writer);
            Helper.jQuery(Component.Id, jQueryPluginName, options);
        }
    }
}
